<?php

/**
 * @desc		框架扩展：数据库操作类库|Pdo类型（关系型数据库）操作接口类
 * ---------------------------------------------------------------------
 * @author	unphp <unphp@qq.com>
 * @date		2014-03-27
 * @copyright	UnPHP 1.1
 * ---------------------------------------------------------------------
 */

namespace UnPHP\Lib\DBdriver;

use PDO;

//use DBInterface;

class PdoEngine implements DBInterface
{

        private $_last_error_info = null;
        private $_conn = null;

        /**
         *
         * @var PoolClient 
         */
        private $_poolClient = null;
        private $_engineCustom = null;
        private $_filed_type = array(
            'int' => 'int',
        );

        public function __construct(PoolClient $poolClient)
        {
                $this->_poolClient = $poolClient;
        }

        private function _getPoolClient()
        {
                return $this->_poolClient;
        }

        protected function _getConn()
        {
                if (null === $this->_conn)
                {
                        $dsn = $this->_getPoolClient()->engineType . ":dbname=" . $this->_getPoolClient()->dbname . ";host=" . $this->_getPoolClient()->host;
                        if (null != $this->_getPoolClient()->port)
                        {
                                $dsn .=";port=" . $this->_getPoolClient()->port;
                        }
                        $this->_conn  = new PDO($dsn, $this->_getPoolClient()->user, $this->_getPoolClient()->password, array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES '" . $this->_getPoolClient()->charset . "'"));
                        $engine_class = 'Pdo' . ucfirst($this->_getPoolClient()->engineType);
                        switch ($engine_class)
                        {
                                case 'PdoMysql':
                                        $this->_engineCustom = new PdoMysql($this->_conn);
                                        break;
                        }
                }
                return $this->_conn;
        }
        
        protected function prepare($sql)
        {
                Pool::traceSql($sql);
                return $this->_getConn()->prepare($sql);
        }

        protected function _getCollection($collectionName)
        {
                return $this->_getPoolClient()->prefix . $collectionName;
        }

        public function getPk($collectionName)
        {
                
        }

        public function errorInfo()
        {
                return $this->_last_error_info;
        }

        public function begin()
        {
                return $this->_getConn()->beginTransaction();
        }

        public function commit()
        {
                return $this->_getConn()->commit();
        }

        public function rollback()
        {
                return $this->_getConn()->rollBack();
        }

        public function findOne($collectionName, $condition = array(), $options = array())
        {
                try
                {
                        $bind_params = array();
                        $where       = $this->_querySql($condition, $bind_params);
                        $filed       = $this->getFields($options);
                        $order       = $this->getOrder($options);
                        //$limit = $this->getLimit($options);
                        $sql         = "SELECT " . $filed . " FROM " . $this->_getCollection($collectionName) . $where . $order . 'limit 1';
                        $sth         = $this->prepare($sql);
                        //var_dump($sql);exit;
                        foreach ($bind_params as $key => $v)
                        {
                                $value = $v;
                                if (is_array($v))
                                {
                                        $value = "(" . implode(",", $v) . ")";
                                }
                                $sth->bindValue($key, $value);
                        }
                        $sth->execute();
                        $rs = $sth->fetch(PDO::FETCH_ASSOC);
                        
                        return $rs;
                }
                catch (Exception $exc)
                {
                        echo $exc->getTraceAsString();
                }
        }

        public function findAll($collectionName, $condition = array(), $options = array())
        {
                $bind_params = array();
                $where       = $this->_querySql($condition, $bind_params);
                $filed       = $this->getFields($options);
                $order       = $this->getOrder($options);
                $limit       = $this->getLimit($options);
                $sql         = "select " . $filed . " FROM " . $this->_getCollection($collectionName) . $where . $order . $limit;
                $sth         = $this->prepare($sql);
                foreach ($bind_params as $key => $v)
                {
                        $value = $v;
                        if (is_array($v))
                        {
                                $value = "(" . implode(",", $v) . ")";
                        }
                        $sth->bindValue($key, $value);
                }
                $sth->execute();
                $rs = $sth->fetchAll(PDO::FETCH_ASSOC);
                return $rs;
        }

        public function count($collectionName, $condition = array())
        {
                $bind_params = array();
                $where       = $this->_querySql($condition, $bind_params);
                $filed       = 'count(*) as num';
                $sql         = "SELECT " . $filed . " FROM " . $this->_getCollection($collectionName) . $where;
                $sth         = $this->prepare($sql);
                foreach ($bind_params as $key => $v)
                {
                        $value = $v;
                        if (is_array($v))
                        {
                                $value = "(" . implode(",", $v) . ")";
                        }
                        $sth->bindValue($key, $value);
                }
                $sth->execute();
                $rs = $sth->fetch(PDO::FETCH_ASSOC);
                return $rs['num'];
        }

        public function insert($collectionName, $new, $options = array())
        {
                if (is_array($new) && !empty($new))
                {
                        $conn   = $this->_getConn();
                        $fileds = $this->_engineCustom->get_table_filed_type($this->_getCollection($collectionName));
                        $sql    = $this->getInsertSql($this->_getCollection($collectionName), $fileds);
                        $sth    = $conn->prepare($sql);
                        $data   = array();
                        $rs     = array();
                        foreach ($fileds['field'] as $filed => $v)
                        {
                                if ($fileds['auto_key_id'] == $filed)
                                {
                                        continue;
                                }
                                $data[':' . $filed] = $this->get_field_value($v['type'], isset($new[$filed]) ? $new[$filed] : $v['default']);
                                $rs[$filed]         = $this->get_field_value($v['type'], isset($new[$filed]) ? $new[$filed] : $v['default']);
                        }
                        if ($sth->execute($data))
                        {
                                return $conn->lastInsertId();
                        }
                        else
                        {
                                $this->_setLastErrorInfo($sth->errorInfo());
                                return FALSE;
                        }
                }
                else
                {
                        return FALSE;
                }
        }

        public function batchInsert($collectionName, $new, $options = array())
        {
                $fileds = $this->_engineCustom->get_table_filed_type($this->_getCollection($collectionName));
                $sth    = $this->prepare($this->getInsertSql($this->_getCollection($collectionName), $fileds));
                foreach ($new as $value)
                {
                        $data = array();
                        foreach ($fileds['field'] as $filed => $v)
                        {
                                if ($fileds['auto_key_id'] == $filed)
                                {
                                        continue;
                                }
                                $data[':' . $filed] = $this->get_field_value($v['type'], isset($value[$filed]) ? $value[$filed] : $v['default']);
                        }
                        $sth->execute($data);
                }
        }

        public function remove($collectionName, $condition = array(), $options = array())
        {
                $bind_params = array();
                $where       = $this->_querySql($condition, $bind_params);
                $order       = $this->getOrder($options);
                $limit       = $this->getLimit($options);
                $sql         = "DELETE FROM " . $this->_getCollection($collectionName) . $where . $order . $limit;
                $sth         = $this->prepare($sql);
                foreach ($bind_params as $key => $value)
                {
                        $sth->bindValue($key, $value);
                }
                $sth->execute();
                $colcount = $sth->rowCount();
                return $colcount;
        }

        public function update($collectionName, $condition, $new, $options = array())
        {
                if ($new)
                {
                        $bind_params = array();
                        $where       = $this->_querySql($condition, $bind_params);
                        $set         = "";
                        $key_str     = "set_";
                        $i           = 0;
                        if (isset($new['set']))
                        {
                                $setList = $new['set'];
                                foreach ($setList as $k => $v)
                                {
                                        $temp_key               = $key_str . $i++;
                                        $set .= $k . "=:" . $temp_key . ",";
                                        $bind_params[$temp_key] = $v;
                                }
                        }
                        if (isset($new['inc']))
                        {
                                $incList = $new['inc'];
                                foreach ($incList as $k => $v)
                                {
                                        $set .= $k . "=" . $k . "+" . $v . ",";
                                }
                        }
                        $set = substr($set, 0, -1);
                        $sql = "UPDATE " . $this->_getCollection($collectionName) . " SET " . $set . $where;
                        $sth = $this->prepare($sql);
                        foreach ($bind_params as $key => $value)
                        {
                                $sth->bindValue($key, $value);
                        }
                        $sth->execute();
                        $colcount = $sth->rowCount();
                        return $colcount;
                }
        }

        private function getFields($options)
        {
                $fields = isset($options['fields']) && count($options['fields']) > 0 ? implode(',', $options['fields']) : "*";
                return $fields;
        }

        private function getOrder($options)
        {
                $order = "";
                if (isset($options['sort']) && is_array($options['sort']) && !empty($options['sort']))
                {
                        $order = " Order by ";
                        foreach ($options['sort'] as $key => $value)
                        {
                                if ($value == 1)
                                {
                                        $order .= $key . " ASC,";
                                }
                                else
                                {
                                        $order .= $key . " DESC,";
                                }
                        }
                        $order = substr($order, 0, -1) . " ";
                }
                return $order;
        }

        private function getLimit($options)
        {
                $limits = "";
                if (isset($options['offset']) || isset($options['limit']))
                {
                        $limits = " Limit ";
                        if (isset($options['offset']))
                        {
                                $limits .= intval($options['offset']) . " ";
                        }
                        else
                        {
                                $limits .= " 0 ";
                        }
                        if (isset($options['limit']) && $options['limit'] > 0)
                        {
                                $limits .= "," . intval($options['limit']) . " ";
                        }
                }
                return $limits;
        }

        private function _querySql($condition = array(), &$bind_params = array())
        {
                try
                {
                        $where   = "";
                        $key_str = "where_";
                        $i       = 0;
                        if ($condition)
                        {
                                //and
                                if (isset($condition[ParseQuery::TAG_AND]))
                                {
                                        $andList = $condition[ParseQuery::TAG_AND];
                                        foreach ($andList as $k => $tagList)
                                        {
                                                foreach ($tagList as $tag => $vList)
                                                {
                                                        $sign = $this->sign_to_sql($tag);
                                                        if ($sign)
                                                        {
                                                                foreach ($vList as $v)
                                                                {
                                                                        $temp_key               = $key_str . $i++;
                                                                        $where .= $k . $sign . ':' . $temp_key . ' AND ';
                                                                        $bind_params[$temp_key] = $v;
                                                                }
                                                        }
                                                }
                                        }
                                }
                                //in
                                if (isset($condition[ParseQuery::TAG_IN]))
                                {
                                        $inList = $condition[ParseQuery::TAG_IN];
                                        foreach ($inList as $k => $vList)
                                        {
                                                $temp_key = $key_str . $i++;
                                                $where .= $k . ' in (';
                                                foreach ($vList as $v)
                                                {
                                                        $temp_key               = $key_str . $i++;
                                                        $bind_params[$temp_key] = $v;
                                                        $where .= ':' . $temp_key . ',';
                                                }
                                                $where = substr($where, 0, -1) . ") AND ";
                                        }
                                }
                                return ' WHERE ' . substr($where, 0, -4);
                        }
                        else
                        {
                                return $where;
                        }
                }
                catch (Exception $exc)
                {
                        echo $exc->getTraceAsString();
                }
        }

        private function _setLastErrorInfo($errorInfo)
        {
                $this->_last_error_info = $errorInfo;
                Pool::throwError($errorInfo);
        }

        private function sign_to_sql($str)
        {
                $rs = '';
                switch ($str)
                {
                        case ParseQuery::TAG_EQ:
                                $rs = '=';
                                break;
                        case ParseQuery::TAG_GT:
                                $rs = '>';
                                break;
                        case ParseQuery::TAG_LT:
                                $rs = '<';
                                break;
                        case ParseQuery::TAG_GTE:
                                $rs = '>=';
                                break;
                        case ParseQuery::TAG_LTE:
                                $rs = '<=';
                                break;
                        case ParseQuery::TAG_NE:
                                $rs = '<>';
                                break;
                }
                if (empty($rs))
                        return false;
                return $rs;
        }

        private function get_field_value($type, $value)
        {
                $rs = null;
                switch ($type)
                {
                        case 'int':
                                $rs = intval($value);
                                break;
                        case 'float':
                                $rs = floatval($value);
                                break;
                        case 'string':
                                $rs = strval($value);
                                break;
                        default:
                                $rs = strval($value);
                                break;
                }
                return $rs;
        }

        private function getInsertSql($collectionName, $fileds)
        {
                $insert_files  = "(";
                $insert_values = "(";
                foreach ($fileds['field'] as $k => $v)
                {
                        if ($fileds['auto_key_id'] == $k)
                        {
                                continue;
                        }
                        $insert_files .= $k . ',';
                        $insert_values .= ':' . $k . ',';
                }
                $insert_files  = substr($insert_files, 0, -1) . ') ';
                $insert_values = substr($insert_values, 0, -1) . ')';
                $sql           = "INSERT INTO `{$collectionName}` " . $insert_files . 'value ' . $insert_values;
                return $sql;
        }

}
